﻿using System;
using System.Collections.Generic;
using System.Threading;

namespace IBNet
{
   /// <summary>
   /// An event-like class that uses a thread pool thread to service the event handler
   /// attached to the contained event.
   /// </summary>
   /// <typeparam name="T">The type of EventArgs for this event</typeparam>
   public class EventServicedByThreadPool<T> : IDisposable where T : EventArgs
   {
      private event EventHandler<T> _event;
      private AutoResetEvent _eventLockAcquired;
      private bool _hasAnEventHandlerAttached;

      /// <summary>
      /// Creates a new EventServicedByThreadPool.
      /// </summary>
      public EventServicedByThreadPool()
      {
      }

      /// <summary>
      /// Gets a value specifying whether this EventServicedByThreadPool 
      /// has an event handler attached.
      /// </summary>
      public bool HasAnEventHandlerAttached { get { return _hasAnEventHandlerAttached; } }

      /// <summary>
      /// Attaches an event handler to this EventServicedByThreadPool.
      /// </summary>
      /// <param name="eventHandler">An event handler to attach to this EventServicedByThreadPool.</param>
      public void AttachEventHandler(EventHandler<T> eventHandler)
      {
         if (_hasAnEventHandlerAttached)
            throw new InvalidOperationException("Can only attach one event handler.");

         _event += eventHandler;
         _eventLockAcquired = new AutoResetEvent(true);
         _hasAnEventHandlerAttached = true;
      }

      /// <summary>
      /// Detaches an event handler from this EventServicedByThreadPool.
      /// </summary>
      /// <param name="eventHandler">An event handler to detach from this EventServicedByThreadPool.</param>
      public void DetachEventHandler(EventHandler<T> eventHandler)
      {
         if (!_hasAnEventHandlerAttached)
            return;

         _event -= eventHandler;
         _eventLockAcquired.Close();         
         _hasAnEventHandlerAttached = false;
      }

      /// <summary>
      /// Calls the contained event on a thread pool thread.
      /// </summary>
      /// <remarks>
      /// This method permits only one thread pool thread at a time to execute
      /// the event handler attached to the contained event.
      /// This method does this to ensure that call 1 to CallEvent with EventArgs e1
      /// is processed before call 2 to CallEvent with EventArgs e2.
      /// </remarks>
      /// <param name="e">Event arguments for the event handler.</param>
      public void CallEvent(T e)
      {
         if (!_hasAnEventHandlerAttached)
            return;

         _eventLockAcquired.WaitOne();

         ThreadPool.UnsafeQueueUserWorkItem(delegate
         {
# if DEBUG
            // Thread name = "X Thread" if typeof(T).Name == "XEventArgs".
            Thread.CurrentThread.Name = typeof(T).Name.Split(
               new string[] { "EventArgs" }, StringSplitOptions.None)[0] + " Thread";
# endif

            lock (_event)
            {
               _eventLockAcquired.Set();
               EventHandler<T> handler = _event;
               if (handler != null)
               {
                  handler(this, e);
               }
            }
         }, null);
      }

      /// <summary>
      /// Disposes of an AutoResetEvent used by this EventServicedByThreadPool.
      /// </summary>
      public void Dispose()
      {
         Dispose(true);
         GC.SuppressFinalize(this);
      }

      protected virtual void Dispose(bool disposing)
      {
         if (disposing)
         {
            _eventLockAcquired.Close();
         }
      }
   }
}
